#!/usr/bin/env python3
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function
import argparse
import distutils
import errno
import grp
import os
import readline
import sys
import shutil
import subprocess
from pipes import quote
from subprocess import check_call

try:
  from shutil import which as find_executable
except AttributeError:
  from distutils.spawn import find_executable

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.path.join(REPO_ROOT, 'infra', 'ci'))
from config import JOB_CONFIGS, SANDBOX_IMG

try:
  input = raw_input
except NameError:
  pass


def user_in_docker_group():
  try:
    group = grp.getgrnam('docker')
  except KeyError:
    return False
  else:
    return group.gr_gid in os.getgroups()


def decision(question='Would you like to continue', confirm=True, default='n'):
  default = default.lower().strip()
  yes = default in {'y', 'yes'}
  no = default in {'n', 'no'}
  default = 'y' if yes else 'n'
  prompt = '%s? [%s/%s]: ' % (question, 'Y' if yes else 'y', 'N' if no else 'n')
  if not confirm:
    print('%sy' % prompt)
    return
  while True:
    choice = input(prompt).lower().strip()
    if not choice:
      choice = default
    if choice in {'y', 'yes'}:
      return
    elif choice in {'n', 'no'}:
      sys.exit(3)


def main():
  parser = argparse.ArgumentParser(
      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  parser.add_argument('config', choices=JOB_CONFIGS.keys())
  parser.add_argument(
      '--runner',
      help='The container runner executable to use',
      choices=('podman', 'docker'),
      default='podman' if find_executable('podman') else 'docker')
  parser.add_argument(
      '--build',
      action='store_true',
      help='Will perform a build of sandbox image')
  group = parser.add_mutually_exclusive_group()
  group.add_argument(
      '--confirm',
      action='store_true',
      default=True,
      help='User confirmation of decision prompts')
  group.add_argument(
      '--no-confirm',
      dest='confirm',
      action='store_false',
      help='Forces confirmation of decision prompts')
  args = parser.parse_args()

  # Check that the directory is clean.
  git_cmd = ['git', '-C', REPO_ROOT, 'status', '--porcelain']
  modified_files = subprocess.check_output(git_cmd).decode()
  if modified_files:
    print('The current Git repo has modified/untracked files.')
    print('The sandboxed VM will fetch the HEAD of your current git repo.')
    print('This is probably not the state you want to be in.')
    print('I suggest you stop, commit and then re-run this script')
    print('Modified files:\n' + modified_files)
    decision('Do you know what you are doing', confirm=args.confirm)

  if args.build:
    print('')
    print('About to build %r locally with %r' % (args.image, args.runner))
    decision(confirm=args.confirm)
    check_call(('make', '-C', os.path.join(REPO_ROOT, 'infra', 'ci'),
                'BUILDER=%s' % args.runner, 'build-sandbox'))

  bundle_path = '/tmp/perfetto-ci.bundle'
  check_call(['git', '-C', REPO_ROOT, 'bundle', 'create', bundle_path, 'HEAD'])
  os.chmod(bundle_path, 0o664)
  env = {
      'PERFETTO_TEST_GIT_REF': bundle_path,
  }
  env.update(JOB_CONFIGS[args.config])

  workdir = os.path.join(REPO_ROOT, 'out', 'tmp.ci')
  cmd = []
  if args.runner == 'docker' and not user_in_docker_group():
    cmd += ['sudo', '--']
  cmd += [
      args.runner, 'run', '-it', '--name', 'perfetto_ci', '--cap-add',
      'SYS_PTRACE', '--rm', '--volume',
      '%s:/ci/ramdisk' % workdir, '--tmpfs', '/tmp:exec',
      '--volume=%s:%s:ro' % (bundle_path, bundle_path)
  ]
  for kv in env.items():
    cmd += ['--env', '%s=%s' % kv]
  cmd += [SANDBOX_IMG]
  cmd += [
      'bash', '-c',
      'cd /ci/ramdisk; bash /ci/init.sh || sudo -u perfetto -EH bash -i'
  ]

  print(
      'About to run\n',
      ' '.join('\n  ' + c if c.startswith('--') or c == 'bash' else quote(c)
               for c in cmd))
  print('')
  print('The VM workdir /ci/ramdisk will be mounted into: %s' % workdir)
  print('The contents of %s will be deleted before starting the VM' % workdir)
  decision(confirm=args.confirm)

  try:
    shutil.rmtree(workdir)
  except EnvironmentError as e:
    if e.errno == errno.ENOENT:
      pass
    elif e.errno == errno.EACCES:
      print('')
      print('Removing previous volume %r' % workdir)
      check_call(('sudo', 'rm', '-r', quote(workdir)))
    else:
      raise

  os.makedirs(workdir)
  os.execvp(cmd[0], cmd)


if __name__ == '__main__':
  sys.exit(main())
